package com.ydl.shopping.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONUtil;
import com.ydl.shopping.annotation.Logger;
import com.ydl.shopping.core.enums.ResultEnum;
import com.ydl.shopping.core.vo.ResultVO;
import com.ydl.shopping.domain.TbLog;
import com.ydl.shopping.service.ITbLogService;
import com.ydl.shopping.utils.ByteUtil;
import com.ydl.shopping.utils.SecurityUtil;
import com.ydl.shopping.utils.ip.AddressUtil;
import com.ydl.shopping.utils.ip.IpUtil;
import com.ydl.shopping.utils.web.WebUtil;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executor;

@Slf4j
@Aspect
@Component
public class LoggerAspect {
    private final ThreadLocal<TbLog> logThreadLocal = new ThreadLocal<>();

    @Resource
    private Executor customThreadPoolTaskExecutor;

    @Resource
    private ITbLogService logService;

    @Resource
    private AddressUtil addressUtils;

    @Pointcut("@annotation(logger)")
    public void pointCutLog(Logger logger) {}

    /**
     * 前置通知
     */
    @Before(value = "pointCutLog(logger)", argNames = "joinPoint,logger")
    public void doBefore(JoinPoint joinPoint, Logger logger) {
        HttpServletRequest request = WebUtil.getRequest();
        assert request != null;
        // 获取请求头中的User-Agent
        String userAgent = request.getHeader("User-Agent");
        //解析agent字符串
        UserAgent agent = UserAgent.parseUserAgentString(userAgent);
        //获取浏览器对象
        Browser browser = agent.getBrowser();
        //获取操作系统对象
        OperatingSystem operatingSystem = agent.getOperatingSystem();

        // 操作系统
        String os = operatingSystem.getName();
        // 登录浏览器
        String browserName = browser.getName();
        // 获取请求方法
        String requestMethod = request.getMethod();
        // 获取请求URI
        String requestURI = request.getRequestURI();
        // 获取IP地址
        String ipAddress = IpUtil.getIpAddress(request);
        String location = addressUtils.getIpAddress(ipAddress);
        // 获取请求用户名
        String username = SecurityUtil.getUserName();
        // 获取操作模块
        String model = logger.model();
        // 获取操作类别
        int operatorType = logger.operatorType();
        // 获取是否记录请求参数
        boolean recordRequestParam = logger.isRecordRequestParam();

        // 获取目标方法名
        String methodName = joinPoint.getSignature().toString();

        // 获取方法类名
        String methodClassName = joinPoint.getSignature().getName();

        int businessType = 0; // 查询
        if (methodClassName.startsWith("save")) {
            businessType = 1; // 新增
        } else if (methodClassName.startsWith("update")) {
            businessType = 2; // 修改
        } else if (methodClassName.startsWith("delete")) {
            businessType = 3; // 删除
        } else if (methodClassName.startsWith("export")) {
            businessType = 4; // 导出
        } else if (methodClassName.startsWith("import")) {
            businessType = 5; // 导入
        } else if (methodClassName.startsWith("empty")) {
            businessType = 6; // 清空
        }

        TbLog tbLog = new TbLog();
        tbLog.setModel(model);
        tbLog.setBusinessType(businessType);
        tbLog.setMethodName(methodName);
        tbLog.setOperatorUser(username);
        tbLog.setOperatorType(operatorType);
        tbLog.setRequestMethod(requestMethod);
        tbLog.setRequestURI(requestURI);
        tbLog.setRequestIp(ipAddress);
        tbLog.setServerLocation(location);

        if (recordRequestParam) {
            Object[] args = joinPoint.getArgs();
            tbLog.setRequestParam(JSONUtil.toJsonStr(args));
        }

        tbLog.setStartTime(new Date());
        tbLog.setBrowserName(browserName);
        tbLog.setOs(os);

        logThreadLocal.set(tbLog);

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        log.info("========================================[请求进入]========================================");
        log.info("IP：{} URI：{} 请求时间：{} 请求模块：{}", ipAddress, requestURI, simpleDateFormat.format(tbLog.getStartTime()), model);
    }

    /**
     * 返回通知
     */
    @AfterReturning(pointcut = "pointCutLog(logger)", returning = "result", argNames = "logger,result")
    public void doAfterReturning(Logger logger, Object result) {
        boolean recordResponseData = logger.isRecordResponseData();

        TbLog tbLog = logThreadLocal.get();
        tbLog.setEndTime(new Date());
        long startTime = tbLog.getStartTime().getTime();
        long endTime = tbLog.getEndTime().getTime();
        tbLog.setExecuteTime(endTime - startTime);

        if (recordResponseData) {
            /*
              HuTool核心工具类——Convert 万能转换器
             */
            ResultVO resultVO = Convert.convert(ResultVO.class, result);
            if (resultVO == null || resultVO.getCode() == ResultEnum.SUCCESS.getCode()) {
                tbLog.setStatus(0);
            } else {
                tbLog.setStatus(1);
                tbLog.setExceptionInfo(resultVO.getMessage());
            }
            tbLog.setResponseData(JSONUtil.toJsonStr(resultVO));
        }
        end(tbLog, logService);
    }

    /**
     * 异常通知
     */
    @AfterThrowing(pointcut = "pointCutLog(logger)", throwing = "e", argNames = "logger,e")
    public void doAfterThrowing(Logger logger, Throwable e) {
        TbLog tbLog = logThreadLocal.get();
        tbLog.setEndTime(new Date());
        long startTime = tbLog.getStartTime().getTime();
        long endTime = tbLog.getEndTime().getTime();
        tbLog.setExecuteTime(endTime - startTime);
        tbLog.setStatus(1);
        tbLog.setExceptionInfo(e.getMessage());
        end(tbLog, logService);
    }

    private void end(TbLog tbLog, ITbLogService logService) {
        customThreadPoolTaskExecutor.execute(new SaveLogThread(tbLog, logService));
        logThreadLocal.remove();

        Runtime runtime = Runtime.getRuntime();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.info(
                "结束时间：{} 用时：{}ms URI：{} 总内存：{} 已用内存：{}",
                simpleDateFormat.format(tbLog.getEndTime()),
                tbLog.getExecuteTime(),
                tbLog.getRequestURI(),
                ByteUtil.formatByteSize(runtime.totalMemory()),
                ByteUtil.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
        log.info("========================================[请求结束]========================================");
    }

    private static class SaveLogThread extends Thread {
        private final TbLog tbLog;
        private final ITbLogService logService;

        public SaveLogThread(TbLog tbLog, ITbLogService logService) {
            this.tbLog = tbLog;
            this.logService = logService;
        }

        @Override
        public void run() {
            logService.save(tbLog);
        }
    }

}
